//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2013 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_AUTO_MEMORY_H_
#define _BUOLA_AUTO_MEMORY_H_

#include <buola/auto/debug.h>
#include <buola/auto/exceptions.h>
#include <buola/auto/traits.h>

#include <memory>
#include <cstddef>
#include <cstdint>
#include <climits>
#include <sys/types.h>
#include <cstdlib>
#include <cstring>
#include <array>

namespace buola {

///\addtogroup memory
///@{

///\name Memory allocation
//@{
    
///default memory alignment
///
///Twice the size of a pointer is good enough for both 32 and 64-bit systems
static const std::size_t cDefaultMemAlignment=2*sizeof(void*);

///allocates \p pSize bytes of memory
///
///It doesn't zero the memory
///\throw XMemory if there is no memory available
///
///The memory must be freed with \ref free
inline void* alloc_mem(std::size_t pSize)
{
    void *lResult=malloc(pSize);
    if(!lResult)
    {
        back_trace();
        throw XMemory("No virtual memory available in malloc");
    }

    return lResult;
}

///allocates \p pSize bytes of memory
///
///It also zeroes the memory
///\throw XMemory if there is no memory available
///
///The memory must be freed with \ref free
inline void* alloc_mem_z(std::size_t pSize)
{
    void *lResult=calloc(1,pSize);
    if(!lResult)
    {
        back_trace();
        throw XMemory("No virtual memory available in calloc");
    }

    return lResult;
}

///allocates \p pSize bytes of memory (alignment version)
///
///It doesn't zero the memory
///\throw XMemory if there is no memory available
///
///The memory must be freed with \ref free
inline void* alloc_mem(std::size_t pSize,std::size_t pAlign)
{
#if BUOLA_PLATFORM_WINDOWS
    ///\todo find a better solution
    return alloc_mem(pSize);
#else
    void *lResult;
    if(posix_memalign(&lResult,pAlign,pSize))
    {
        back_trace();
        throw XMemory("No virtual memory available in malloc");
    }
    return lResult;
#endif
}

///changes the size of the memory block pointed by \p pMem so that it can hold \p pN objects of type \p tType
///
///\param pMem the previous block of memory. It must either be nullptr or a pointer previously returned by one
///of the allocation functions in this section
///\param pSize the new size
inline void* realloc_mem(void *pMem,std::size_t pSize)
{
    void *lResult=realloc(pMem,pSize);
    if(!lResult)
    {
        back_trace();
        throw XMemory("No virtual memory available in realloc");
    }

    return lResult;
}

///allocates and returns a copy of the pSize bytes starting at pMem
///
///The memory must be freed with \ref free
inline void* dup_mem(const void *pMem,std::size_t pSize)
{
    void *lNew=alloc_mem(pSize);
    return memcpy(lNew,pMem,pSize);
}

///allocates size for \p pN objects of type \p tType
///
///It doesn't zero the memory. Other than this, it is identical to \ref alloc_array_z
///\throw XMemory if there is no memory available
///
///The memory must be freed with \ref free
template <typename tType>
inline tType* alloc_array(std::size_t pN)
{
    return (tType*)alloc_mem(pN*sizeof(tType));
}

///allocates size for \p pN objects of type \p tType (alignment version)
///
///It doesn't zero the memory. Other than this, it is identical to \ref alloc_array_z
///\throw XMemory if there is no memory available
///
///The memory must be freed with \ref free
template <typename tType>
inline tType* alloc_array(std::size_t pN,std::size_t pAlign)
{
    return (tType*)alloc_mem(pN*sizeof(tType),pAlign);
}

///allocates memory for \p pNum objects of type \p tType
///
///It also zeroes the memory
///\throw XMemory if there is no memory available
///
///The memory must be freed with \ref free
template<typename tType>
inline tType* alloc_array_z(std::size_t pN=1)
{
    return (tType*)alloc_mem_z(pN*sizeof(tType));
}

///changes the size of the memory block pointed by \p pMem so that it can hold \p pN objects of type \p tType
///
///It updates the pMem pointer to point to the new area of memory, which might start at a different position
///
///\throw XMemory if there is no memory available
template<typename tType>
inline void realloc_array(tType* &pMem,int pN)
{
    pMem=(tType*)realloc_mem(pMem,pN*sizeof(tType));
}

///duplicates the array of pN objects of type tType
///
///Returns the newly allocated copy of the array
///\throw XMemory if there is no memory available
///
///The memory must be freed with \ref free
template <typename tType>
inline tType* dup_array(tType *pSrc,int pN)
{
    tType *lResult=alloc_array<tType>(pN);
    memcpy(lResult,pSrc,pN*sizeof(tType));
    return lResult;
}

///copies \p pSrc array into \p pDst
///
///It copies \p pN elements of type \p tType.
///
///The copy is performed as an array of bytes, so no constructor or assignment operator is called. Because
///of this, it should only be used for trivially-copiable objects.
///
///\todo Use \c is_trivially_copiable once it is in the standard library to verify that
template <typename tType>
inline void copy_array(const tType *pSrc,tType *pDst,int pN)
{
    memcpy(pDst,pSrc,pN*sizeof(tType));
}

///moves part of an array into another part of itself
///
///It moves the range (\p pFromStart,\p pFromEnd) to the \p pTo position.
///
///The move is performed as an array of bytes, so no constructor or assignment operator is called. Because
///of this, it should only be used for trivially-copiable objects.
///
///\todo Use \c is_trivially_copiable once it is in the standard library to verify that
template <typename tType>
inline void move_array(tType *pArray,std::size_t pFromStart,std::size_t pFromEnd,std::size_t pTo)
{
    memmove(pArray+pTo,pArray+pFromStart,(pFromEnd-pFromStart)*sizeof(tType));
}

///frees the memory pointed by \p pMem and sets \p pMem to \c nullptr
///
///It should be used to free memory allocated by other functions in this section.
template<typename tType>
inline void free(tType* &pMem)
{
    ::free(pMem);
    pMem=nullptr;
}

///frees the memory pointed by \p pMem
///
///It should be used to free memory allocated by other functions in this section.
template<typename tType>
inline void free(tType* const& pMem)
{
    ::free(pMem);
}

///returns true if pPtr is suitably aligned for SSE operations
template<typename tType>
inline bool is_sse_aligned(tType *pPtr)
{
    return !(ptrdiff_t(pPtr)&0x0f);
}

///returns the next memory location that respects the given alignment.
template<typename tType>
inline tType *get_next_aligned_pos(tType *pSrc,std::size_t pAlignment=cDefaultMemAlignment)
{
    uint8_t *lMem=reinterpret_cast<uint8_t*>(pSrc);
    size_t lMask=size_t(lMem)&(pAlignment-1);
    if(lMask)
        return reinterpret_cast<tType*>(lMem+(pAlignment-lMask));
    return pSrc;
}

///returns the next memory size that respects the given alignment.
template<typename tSize>
inline tSize get_next_aligned_size(tSize pSize,std::size_t pAlignment=cDefaultMemAlignment)
{
    tSize lMask=pSize&(pAlignment-1);
    if(lMask)
        return pSize+(pAlignment-lMask);
    return pSize;
}

///returns the previous memory size that respects the given alignment.
template<typename tSize>
inline tSize get_prev_aligned_size(tSize pSize,std::size_t pAlignment=cDefaultMemAlignment)
{
    return pSize-(pSize&(pAlignment-1));
}

template<typename tType>
OEnableIf<QIsPOD<tType>,void> zero_pod(tType &pPOD)
{
    memset(&pPOD,0,sizeof(tType));
}

//@}

///\name Pointer management
//@{
    
///provides a uniform way to access a raw pointer from any pointer
///
///Overload for smart pointers with a get() function
template<class tType>
inline auto get_raw_pointer(const tType &pP) -> decltype(pP.get())
{
    return pP.get();
}

///provides a uniform way to access a raw pointer from any pointer
///
///Overload for raw pointers
template<class tType>
inline tType* get_raw_pointer(tType *pP)
{
    return pP;
}

///makes a unique pointer to a newly allocated object
///
///This is part of C++14 and should be replaced once it's supported
template<typename tType,typename... tArgs>
inline std::unique_ptr<tType> make_unique(tArgs&&... pArgs)
{
    return std::unique_ptr<tType>(new tType(std::forward<tArgs>(pArgs)...));
}

//@}

///@}

/*namespace buola*/ }

#endif
